﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Helpers.Collection
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using NUnit.Framework;

    [TestFixture]
    public class TestCommonEnumerators
    {
        [Test]
        public void TestNoDuplicatesFilteringEnumerator()
        {
            List<int> ints = new List<int> { 1, 2, 2, 40, 100, 40, 101, 2, 3 };
            IEnumerator<int> enumerator = FilteringEnumerator.NoDuplicates(ints.GetEnumerator());
            enumerator.MoveNext();
            Assert.That(1, Is.EqualTo(enumerator.Current));
            enumerator.MoveNext();
            Assert.That(2, Is.EqualTo(enumerator.Current));
            enumerator.MoveNext();
            Assert.That(40, Is.EqualTo(enumerator.Current));
            enumerator.MoveNext();
            Assert.That(100, Is.EqualTo(enumerator.Current));
            enumerator.MoveNext();
            Assert.That(101, Is.EqualTo(enumerator.Current));
            enumerator.MoveNext();
            Assert.That(3, Is.EqualTo(enumerator.Current));
        }

        [Test]
        public void TestCachingEnunmerator()
        {
            IEnumerator<int> source = Enumerable.Range(0, 10).Select(x => x * 10).GetEnumerator();
            CachingEnumerator<int> caching = new CachingEnumerator<int>(source);

            Assert.That(caching.MovePrevious(), Is.False);
            Assert.That(() => caching.Current, Throws.TypeOf<InvalidOperationException>());

            // Next and previous
            Assert.That(caching.Position, Is.EqualTo(-1));
            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Position, Is.EqualTo(0));
            Assert.That(caching.Current, Is.EqualTo(0));
            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(10));
            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(20));
            Assert.That(caching.Position, Is.EqualTo(2));
            Assert.That(caching.Current, Is.EqualTo(20));
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(10));
            Assert.That(caching.Position, Is.EqualTo(1));
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(0));
            Assert.That(caching.Position, Is.EqualTo(0));
            Assert.That(caching.MovePrevious(), Is.False);
            Assert.That(caching.Position, Is.EqualTo(-1));
            Assert.That(caching.MovePrevious(), Is.False);

            // Positioning
            Assert.That(() => { caching.Position = -1; }, Throws.TypeOf<ArgumentOutOfRangeException>());

            caching.Position = 3;
            // Shouldn't be able to call Current after setting position
            Assert.That(() => caching.Current, Throws.TypeOf<InvalidOperationException>());

            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Position, Is.EqualTo(3));
            Assert.That(caching.Current, Is.EqualTo(30));
            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(40));
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(30));
            Assert.That(caching.Position, Is.EqualTo(3));

            Assert.That(() => { caching.Position = 8; }, Throws.TypeOf<ArgumentOutOfRangeException>());

            caching.Position = 7;
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.MoveNext(), Is.False, "Shouldn't be able to go beyond last item");
            Assert.That(caching.Position, Is.EqualTo(8));
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(70));
            Assert.That(caching.MovePrevious(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(60));

            caching.Position = 0;
            Assert.That(caching.MoveNext(), Is.True);
            Assert.That(caching.Current, Is.EqualTo(0));
        }

        [Test]
        public void TestPagingEnumerator()
        {
            IEnumerator<int> source = new RangeEnumerator(24);
            PagingEnumerator<int> pager = new PagingEnumerator<int>(source, 10);
            Assert.That(pager.Page, Is.EqualTo(-1));
            Assert.That(pager.MoveNext(), Is.True);
            AssertPage(pager.Current, 10, 0);
            Assert.That(pager.Page, Is.EqualTo(0));

            Assert.That(pager.MoveNext(), Is.True);
            AssertPage(pager.Current, 10, 10);
            Assert.That(pager.Page, Is.EqualTo(1));

            Assert.That(pager.MoveNext(), Is.True);
            AssertPage(pager.Current, 4, 20);
            Assert.That(pager.Page, Is.EqualTo(2));

            pager.Page = 1;
            Assert.That(pager.Page, Is.EqualTo(1));
            Assert.That(pager.MoveNext(), Is.True);
            AssertPage(pager.Current, 10, 10);
            Assert.That(pager.MoveNext(), Is.True);
        }

        private void AssertPage(IEnumerator<int> page, int size, int plus)
        {
            for (int i = 0; i < size; i++)
            {
                Assert.That(page.MoveNext(), Is.True);
                Assert.That(page.Current, Is.EqualTo(i + plus));
            }
            Assert.That(page.MoveNext(), Is.False);
        }

        [Test]
        public void TestFirstElement()
        {
            Object object1 = new Object();
            Object object2 = new Object();

            Assert.That(EnumeratorUtil.First(new[] { object1, object2 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(EnumeratorUtil.First(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(() => EnumeratorUtil.First(new object[] { }.GetEnumerator()), Throws.TypeOf<InvalidOperationException>());

            Assert.That(EnumeratorUtil.FirstOrNull(new[] { object1, object2 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(EnumeratorUtil.FirstOrNull(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(EnumeratorUtil.FirstOrNull(new object[] { }.GetEnumerator()), Is.Null);
        }

        [Test]
        public void TestLastElement()
        {
            Object object1 = new Object();
            Object object2 = new Object();

            Assert.That(EnumeratorUtil.Last(new[] { object1, object2 }.GetEnumerator()), Is.EqualTo(object2));
            Assert.That(EnumeratorUtil.Last(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(() => EnumeratorUtil.Last(new object[] { }.GetEnumerator()), Throws.TypeOf<InvalidOperationException>());

            Assert.That(EnumeratorUtil.LastOrNull(new[] { object1, object2 }.GetEnumerator()), Is.EqualTo(object2));
            Assert.That(EnumeratorUtil.LastOrNull(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(EnumeratorUtil.LastOrNull(new object[] { }.GetEnumerator()), Is.Null);
        }

        [Test]
        public void TestSingleElement()
        {
            Object object1 = new Object();
            Object object2 = new Object();

            Assert.That(() => EnumeratorUtil.Single(new[] { object1, object2 }.GetEnumerator()), Throws.TypeOf<InvalidOperationException>());
            Assert.That(EnumeratorUtil.Single(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(() => EnumeratorUtil.Single(new object[] { }.GetEnumerator()), Throws.TypeOf<InvalidOperationException>());

            Assert.That(() => EnumeratorUtil.SingleOrNull(new[] { object1, object2 }.GetEnumerator()), Throws.TypeOf<InvalidOperationException>());
            Assert.That(EnumeratorUtil.SingleOrNull(new[] { object1 }.GetEnumerator()), Is.EqualTo(object1));
            Assert.That(EnumeratorUtil.SingleOrNull(new object[] { }.GetEnumerator()), Is.Null);
        }

        [Test]
        public void GetItemFromEnd()
        {
            IEnumerable<int> ints = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Assert.That(EnumeratorUtil.FromEnd(ints, 0), Is.EqualTo(9));
            Assert.That(EnumeratorUtil.FromEnd(ints, 1), Is.EqualTo(8));
            Assert.That(EnumeratorUtil.FromEnd(ints, 2), Is.EqualTo(7));
        }

        [Test]
        public void FileAsIterator()
        {
            String[] lines = new String[] {
                "first line",
                "second line and we're still good",
                "",
                "last line"};
            string filePath = CreateTextFileWithLines(lines);
            using (var stream = File.OpenRead(filePath))
            {
                try
                {
                    IEnumerable<string> enumerable = EnumeratorUtil.AsEnumerable(stream);
                    CollectionAssert.AreEqual(new List<string>(lines), enumerable.ToList());
                }
                finally
                {

                    File.Delete(filePath);
                }
            }
        }

        private string CreateTextFileWithLines(String[] lines)
        {
            string filePath = Path.GetTempFileName();
            File.AppendAllLines(filePath, lines);
            return filePath;
        }
    }
}
